package org.evil.dog.gateway.filter;

import brave.internal.Nullable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang.StringUtils;
import org.evil.dog.gateway.enums.ErrorEnum;
import org.evil.dog.gateway.user.UserInfo;
import org.evil.dog.gateway.utils.JwtUrsToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Log4j2
@Component
public class AuthTokenCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthTokenCheckGatewayFilterFactory.Config> {
    //系统换行符，用于日志打印
    private final String LINE_SEPARATOR = System.getProperty("line.separator");
    private final String APP_USER_ID = "userId";
    private final String APP_ORG_ID = "orgId";

    //内部系统filter之间使用的变量
    private final String TOKEN_ZONE = "token_zone";
    private final String TOKEN_IAT_TIME = "tokenIatTime";
    private final String SYS_NO_LOGIN_CHECK = "sys_no_login_check";
    private final String ACTUATOR = "actuator";

    //刷新token值
    private final String AUTH_TOKEN = "auth-token";
    //权限值
    private final String AUTH_PERMS = "auth-perms";
    private final String ZOOM_AUTH = "zoom-auth";

    @Autowired
    private LoadBalancerClient loadBalancer;


    /**
     * 系统权限映射表，根据请求api路径映射值
     */
    private Map<String, List<Integer>> SYS_PERMS_MAP = new HashMap<>();
    private long SYS_TOKEN_TIME = System.currentTimeMillis();


    public AuthTokenCheckGatewayFilterFactory() {
        super(Config.class);
        log.info("网关-加载网关用户鉴权过滤器");
    }

    @Data
    public static class Config {
        /**
         * 不需要验证过滤，默认是需要用户验证过滤
         */
        private Boolean enable = true;
        /**
         * 权限验证开关
         */
        private Boolean permsEnable = true;
        /**
         * 不需要过滤的地址
         */
        private List<String> noFilterUrl = new ArrayList<>();
        /**
         * 可以直接通过权限验证地址
         */
        private List<String> accessApi = new ArrayList<>();
        /**
         * 5分钟刷新token（分）
         */
        private int RefTokenItl = 5;
        /**
         * 系统权限映射刷新(分钟)默认24小时
         */
        private int RefPermsItl = 60 * 24;
        /**
         * TOKEN标识
         */
        @Nullable
        private String token;
        /**
         * 加密key
         */
        @Nullable
        private String key;
        /**
         * api文档开放开关，默认不开放
         */
        private Boolean openApiDoc = false;

    }

    @Override
    public GatewayFilter apply(Config config) {
        return new AuthTokenCheckGateWayFilter(config, loadBalancer);
    }

    public class AuthTokenCheckGateWayFilter extends DogGatewayFilter implements GatewayFilter,Ordered {

        private Config config;

        public AuthTokenCheckGateWayFilter(Config config, LoadBalancerClient loadBalancer) {
            this.config = config;
            this.loadBalancer = loadBalancer;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

            //用户鉴权是否开启标志,默认是需要鉴权
            if (!config.getEnable()) {
                return chain.filter(exchange);
            }
            //获取用户token，解析token
            ServerHttpRequest request = exchange.getRequest();
            //用户鉴权过滤地址，一般配置登录鉴权地址请求
            List<String> noFilterUrl = config.getNoFilterUrl();
            String uriPath = request.getURI().getPath();
            if (noFilterUrl.contains(uriPath) || (config.getOpenApiDoc() && uriPath.indexOf("v2/api-docs") != -1) || uriPath.indexOf(ACTUATOR) != -1) {
                StringBuilder stringBuilder = new StringBuilder(LINE_SEPARATOR);
                stringBuilder.append("网关-不需要用户鉴权过滤的地址").append(LINE_SEPARATOR);
                noFilterUrl.forEach((path) -> {
                    stringBuilder.append(path).append(LINE_SEPARATOR);
                });
                stringBuilder.append("网关-当前地址:" + uriPath);
                log.info(stringBuilder.toString());
                //不过滤地址标志，传入下一个过滤器（UserOneLoginCheck需要判断此参数,不验证此地址用户过滤）;
                exchange.getAttributes().put(SYS_NO_LOGIN_CHECK, SYS_NO_LOGIN_CHECK);
                return chain.filter(exchange);
            }
            //用户token解析
            // 从header或是cookie中获取用户token
            JwtUrsToken jwtUrsToken = getToken(request, config.getToken());
            if (jwtUrsToken == null) {
                return authErrSend(exchange);
            }

            Long tokenIatTime = jwtUrsToken.getIat().getTime();
            UserInfo userInfo = new UserInfo();
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                String subInfo = jwtUrsToken.getSubInfo();
                exchange.getAttributes().put(TOKEN_IAT_TIME, tokenIatTime.toString());
                userInfo = objectMapper.readValue(subInfo, UserInfo.class);
                if (userInfo == null) {
                    return authErrSend(exchange);
                } else {
                    if (StringUtils.isBlank(userInfo.getUserId())) {
                        return authErrSend(exchange);
                    }
                }
            } catch (IOException e) {
                log.error("网关-解析用户token错误：", e);
            }

            //权限控制
            String apiPath = exchange.getRequest().getURI().getPath();
            RefToken refToken = authenticate(userInfo, apiPath, tokenIatTime);
            if (!refToken.isAccess()) {
                //权限不过关
                return this.tokenErrSend(exchange, refToken.getToken(), refToken.getPerms());
            }

            //构造用户信息转入服务中header中。
            ServerHttpRequest newRequest = exchange.getRequest();
            ServerHttpRequest.Builder builder = newRequest.mutate();
            //用户Id userId
            if (userInfo.getUserId() != null) {
                builder = builder.header(APP_USER_ID, userInfo.getUserId());
                exchange.getAttributes().put(APP_USER_ID, userInfo.getUserId());
            }
            if (userInfo.getZone() != null) {
//                builder = builder.header(TOKEN_ZONE, userInfo.getZone());
                exchange.getAttributes().put(TOKEN_ZONE, userInfo.getZone());
            }
            //继续增加用户需要的header信息
            newRequest = builder.build();
            return chain.filter(exchange.mutate().request(newRequest).build()).then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                HttpHeaders httpHeaders = response.getHeaders();
                if (refToken.getPerms() != null) {
                    httpHeaders.add(AUTH_PERMS, refToken.getPerms());
                }
                if (refToken.getToken() != null) {
                    httpHeaders.add(AUTH_TOKEN, refToken.getToken());
                }
//                httpHeaders.add("zoom-auth", "xxxyyy");
            }));
        }
        private RefToken authenticate(UserInfo userInfo, String apiPath, Long tokenIatTime) {
            RefToken refToken = new RefToken();
            if (!config.getPermsEnable()) {
                return refToken;
            }
            return refToken;
        }

        @Override
        public int getOrder() {
            return 0;
        }

        private JwtUrsToken getToken(ServerHttpRequest request, String token) {
            String jwt_token = request.getHeaders().getFirst(token);
            if (jwt_token == null || "".equals(jwt_token)) {
                if (request.getCookies().getFirst(token) != null) {
                    jwt_token = request.getCookies().getFirst(token).getValue();
                }
            }
            try {
                JwtUrsToken jwtUrsToken = new JwtUrsToken(config.getKey());
                jwtUrsToken.parseJWT(jwt_token);
                return jwtUrsToken;
            } catch (Exception e) {
                log.debug("网关-用户无效,x_access_token={}", jwt_token);
                return null;
            }
        }

        /**
         * 用户无效输出json
         *
         * @param exchange
         * @return
         */
        private Mono<Void> sendErr(ServerWebExchange exchange, ErrorEnum err, String refToken, String perms, String sendMsg) {
            ServerHttpResponse response = exchange.getResponse();
            ((AbstractServerHttpResponse) response).setStatusCodeValue(err.getCode());
            ObjectMapper objectMapper = new ObjectMapper();
            String resBody = "{}";
            try {
                resBody = objectMapper.writeValueAsString(err.getMsg());
            } catch (JsonProcessingException e) {
                log.error("网关-用户鉴权json解析ErrorEnum.TOKEN_ERR出错", e);
            }
            this.writeErrLog(exchange, err.getInfo());
            HttpHeaders httpHeaders = response.getHeaders();
            httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
            if (refToken != null) {
                httpHeaders.add(AUTH_TOKEN, refToken);
            }
            if (perms != null) {
                httpHeaders.add(AUTH_PERMS, perms);
            }
            if (sendMsg != null) {
                try {
                    String info = "";
                    if (!"".equals(sendMsg)) {
                        info = java.net.URLEncoder.encode(sendMsg, "UTF-8").replace("+", "%20");
                    }
                    httpHeaders.add(ZOOM_AUTH, info);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

            DataBuffer bodyDataBuffer = response.bufferFactory().wrap(resBody.getBytes(Charset.forName("utf-8")));
            return response.writeWith(Mono.just(bodyDataBuffer));
        }

        private Mono<Void> authErrSend(ServerWebExchange exchange) {
            String sendMsg = "无效用户，请重新登录";
            return this.sendErr(exchange, ErrorEnum.AUTH_ERR, null, null, sendMsg);
        }

        private Mono<Void> tokenErrSend(ServerWebExchange exchange, String refToken, String perms) {
            String sendMsg = "";
            return this.sendErr(exchange, ErrorEnum.TOKEN_ERR, refToken, perms, sendMsg);
        }

        @Data
        private class RefToken {
            /**
             * 是否有权限通过true:通过;false:不通过
             */
            private boolean access = true;
            /**
             * 新token，需要客户端刷新token
             */
            private String token;
            /**
             * 新的权限列表，需要客户端进行刷新
             */
            private String perms;
        }
    }

}
